package dviz.visualAspect;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.*;

import dviz.visualAspect.MetaPropertyMap.Type;
import dviz.visualSystem.AnimationSystem;
import dviz.visualSystem.DummyAnimationSystem;
import dviz.visualSystem.impl.AnimationSystemImpl;
import dviz.visualSystem.impl.PropertyQueryable;

/**
 * @author zxq071000
 */
public class DeclarationTranslation {

	// private WeakHashMap modelToVisual;
	// private HashMap<Object, WeakReference> visualToObject;

	// Using encapsulate in case some object can change hashcode like ArrayList
	private Map<Encapsulate, Object> modelToVisual;
	private Map<Object, Encapsulate> visualToObject;
	/**
	 * @uml.property  name="classLoader"
	 * @uml.associationEnd  
	 */
	private VisualizedClassLoader classLoader;
	private Collection<AnimationSystem> animationSystems;
	private static int UNIQUE_ID = 0;

	// Encapsulate class exists because we want put use object whose hashCode is
	// dynamic as the key. So when we put the object in when hashcode is A, and
	// next time we query it, the hashCode will become B, and we will never get
	// the value mapped to the key.
	private static class Encapsulate {
		public Object obj;

		public Encapsulate(Object o) {

			obj = o;
			if (!(o instanceof IndexObject)) {
				throw new RuntimeException("Is not an index object");
			}
		}

		@Override
		public boolean equals(Object in) {
			return obj == ((Encapsulate) in).obj;
		}

		@Override
		public int hashCode() {
			return ((IndexObject) obj).__getIndex();
		}
	}

	public static DeclarationTranslation createByXML(URL XMLInstruct)
			throws Exception {
		DeclarationTranslation declarationTranslation = new DeclarationTranslation();
		declarationTranslation.classLoader = new XMLClassLoader(
				declarationTranslation.new TranslateInvokationHandler(),
				XMLInstruct);
		Vector<String> igList = getIgnoreList();
		if (igList != null)
			for (String pre : igList) {
				System.out
						.println("DeclartionTranslation->createByXML() Ignoring class loading Prefix: "
								+ pre);
				declarationTranslation.classLoader.delegateLoadingOf(pre);
			}
		return declarationTranslation;
	}

	private static Vector<String> getIgnoreList() {

		try {
			URL url = DeclarationTranslation.class.getClassLoader()
					.getResource("ignorelist");
			BufferedReader bufferedReader = new BufferedReader(
					new InputStreamReader(url.openStream()));
			Vector<String> ret = new Vector<String>();
			String line = null;
			while ((line = bufferedReader.readLine()) != null) {
				ret.add(line);
			}
			return ret;
		} catch (Exception e) {
			// TODO Auto-generated catch block
//			e.printStackTrace();
			return null;
		}

	}

	public static DeclarationTranslation createByAnnotation() throws Exception {
		DeclarationTranslation declarationTranslation = new DeclarationTranslation();
		declarationTranslation.classLoader = new AnnotationClassLoader(
				declarationTranslation.new TranslateInvokationHandler());
		Vector<String> igList = getIgnoreList();
		if (igList != null)
			for (String pre : igList) {
				System.out
						.println("DeclartionTranslation->createByXML() Ignoring class loading Prefix: "
								+ pre);
				declarationTranslation.classLoader.delegateLoadingOf(pre);
			}
		return declarationTranslation;
	}

	private DeclarationTranslation() {
		// modelToVisual = new HashMap<Encapsulate, Object>();
		// visualToObject = new HashMap<Object, Encapsulate>();
		modelToVisual = Collections
				.synchronizedMap(new HashMap<Encapsulate, Object>());
		visualToObject = Collections
				.synchronizedMap(new HashMap<Object, Encapsulate>());
		animationSystems = new TreeSet<AnimationSystem>();
	}

	public Object createKey(Object object) {
		if (!(object instanceof IndexObject))
			throw new RuntimeException("Cannot create key for non indexobject");
		((IndexObject) object).__setIndex(UNIQUE_ID++);

		// WeakReference<Object> obj = new WeakReference<Object>(object);
		Object key = new Key(object.getClass().getName());
		visualToObject.put(key, new Encapsulate(object));
		modelToVisual.put(new Encapsulate(object), key);
		return key;
	}

	private boolean isPrimitive(Object o) {
		if (o instanceof Integer || o instanceof Float || o instanceof Boolean
				|| o instanceof Double || o instanceof Long
				|| o instanceof Byte || o instanceof Character
				|| o instanceof Short || o instanceof String)
			return true;
		return false;
	}

	public class TranslateInvokationHandler implements FunctionInvokeHandler {

		@Override
		public void handleFunctionCall(Invokation invokation) {
			HashMap<String, Object> params = new HashMap<String, Object>();
			// tweak the param from key<Param> to key<String>
			// translate all non-primitive object into keys which is used to
			// map into animation system
			for (String key : invokation.getParameters().keySet()) {
				Object obj = invokation.getParameters().get(key);
				if (!isPrimitive(obj)) {
					obj = modelToVisual.get(new Encapsulate(obj));
					if (obj == null)
						throw new RuntimeException(
								"DeclartionTranslation->TranslateInvokationHandler() cannot find corresponding key for \n\t"
										+ invokation.toString());
				}
				params.put(key, obj);
			}
			if (invokation.getType() == Invokation.TYPE_CONSTRUCTOR) {
				Object context = invokation.getContext();
				Object key = createKey(context);
				// Get list of mapped property
				HashMap<String, MetaPropertyMap> propertyList = classLoader
						.getClazzPropertyMap()
						.get(context.getClass().getName());

				String clazzType = invokation.visualObject;
				if (clazzType == null || clazzType.trim().length() == 0) {
					clazzType = context.getClass().getSimpleName();
				}
				if (clazzType == null)
					throw new RuntimeException(
							"Annonymous class with no class mapping definition");

				for (AnimationSystem animationSystem : animationSystems) {
					// If construction map's value is main, than this object is
					// treated as main entry.
					boolean atr = false;
					if (invokation.getConstructType().equalsIgnoreCase("main"))
						atr = true;

					animationSystem.createObject(key, clazzType, params,
							propertyList, atr);
				}
			} else if (invokation.getType() == Invokation.TYPE_METHOD) {
				Object context = invokation.getContext();
				Object key = modelToVisual.get(new Encapsulate(context));
				String actions = invokation.getActionName();
				for (AnimationSystem animationSystem : animationSystems) {
					animationSystem.invokeAction(key, actions, params);
				}
			}
		}
	}

	public void addAnimationSystem(AnimationSystem system) {
		if (system instanceof AnimationSystemImpl) {
			((AnimationSystemImpl) system).getPropertyQuery().add(
					new TranslatePropertyQuery());
		} else {
			((DummyAnimationSystem) system).propertyQueryable = new TranslatePropertyQuery();
		}
		animationSystems.add(system);
	}

	private Object transformArgument(Object ino) {
		if (isPrimitive(ino)) {
			return ino;
		} else {
			Object ano = modelToVisual.get(new Encapsulate(ino));
			if (ano == null)
				throw new RuntimeException(
						"Property Not Mapped to Visual Object"
								+ ino.getClass().getName());
			return ano;
		}
	}

	public class TranslatePropertyQuery implements PropertyQueryable {

		HashMap<Encapsulate, HashMap<String, Object>> propertyCache;

		public TranslatePropertyQuery() {
			propertyCache = new HashMap<Encapsulate, HashMap<String, Object>>();
		}

		@Override
		public Object queryProperty(Object key, MetaPropertyMap property)
				throws Exception {
			// WeakReference objRef = visualToObject.get(key);
			Encapsulate objRef = visualToObject.get(key);
			// if (objRef.get() == null)
			if (objRef == null) {
				throw new RuntimeException("Error object doesn't exist");
			}
			// Object obj = objRef.get();
			Object obj = objRef.obj;
			Object prop = AnnotationHelper.invokeFunction(obj,
					property.functionName);

			// if (property.type == Type.INTERPOLATE
			// /*
			// * || property.type == Type.NODE_FIRST_VALUE || property.type ==
			// * Type.NODE_ON_CHANGE
			// */) {
			// // Object outProp = transformArgument(prop);
			// return prop;
			// } else if (property.type == Type.ONCHANGE) {
			HashMap<String, Object> propCache = propertyCache.get(objRef);
			if (propCache == null) {
				propCache = new HashMap<String, Object>();
				propertyCache.put(objRef, propCache);
			}
			// If the new value is the same as old value, return NULL
			Object cacheValue = propCache.get(property.functionName);
			if (cacheValue != null && cacheValue.equals(prop))
				return null;
			if(obj.getClass().getName().toUpperCase().indexOf("PREDATOR")!=-1){
				System.currentTimeMillis();
			}
			propCache.put(property.functionName, prop);
			// Object outProp = transformArgument(prop);
			return prop;
			// }
//			return null;
		}

		@Override
		public int getPriority() {
			// TODO Auto-generated method stub
			return HIGH;
		}

	}

	public void translateInvoke(String clazz, String method, Object param[])
			throws Exception {
		Class clz = classLoader.loadClass(clazz);
		Method mthd[] = clz.getMethods();
		Method target = null;

		for (Method m : mthd) {
			if (Modifier.isStatic(m.getModifiers())
					&& m.getName().equalsIgnoreCase(method)) {
				target = m;
				break;
			}
		}
		if (target == null)
			throw new Exception("Method not found");
		target.invoke(null, param);
	}

	// public static DeclarationTranslation createTranslator() {
	// DeclarationTranslation declarationTranslation = new
	// DeclarationTranslation();
	// return declarationTranslation;
	// }
}
